//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//	CSaveFile manager class
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
#include "savegame.hpp"
#include "parser/string.hpp"
#include <malloc.h>
#include <string.h>


//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//	CSaveFile manager class
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
CSaveFile::CSaveFile()
{
	blockList   = 0;
	objectList  = 0;
	numBlocks   = maxBlocks  = 0;
	numObjects  = maxObjects = 0;
	pLoad       = 0;
	pSave       = 0;
	saveVersion = 0;
	disableIO   = false;
}

CSaveFile::~CSaveFile()
{
	Reset();
}

//----------------------------------------------------------------------------
//	get the text for the error code
//----------------------------------------------------------------------------
const char *CSaveFile::GetErrorText( ESaveError err )
{
	switch(err)
	{
	case SAVEERR_OK:					return "No error";
	case SAVEERR_BUSY:					return "Busy";
	case SAVEERR_FILENOTFOUND:			return "File not found";
	case SAVEERR_CANNOTCREATEFILE:		return "Cannot create file";
	case SAVEERR_IOERROR:				return "IO error";
	case SAVEERR_WRONGMODE:				return "Wrong mode for this operation";
	case SAVEERR_MEMORYBLOCKMISMATCH:	return "Declared memory blocks do not match saved blocks";
	case SAVEERR_IODISABLED:			return "IO is currently disabled";
	case SAVEERR_OLDVERSION:			return "This file was saved with an old version of the object templates";
	}

	return "Unknown error";
}

//----------------------------------------------------------------------------
//	reset - frees memory
//----------------------------------------------------------------------------
void CSaveFile::Reset( bool closeFile )
{
	if( closeFile )
	{
		if( pLoad )
		{
			delete pLoad;
			pLoad = 0;
		}
		if( pSave )
		{
			delete pSave;
			pSave = 0;
		}
	}

	if( blockList )
	{
		maxBlocks = 0;
		numBlocks = 0;
		free(blockList);
		blockList = 0;
	}
	if( objectList )
	{
		maxObjects = 0;
		numObjects = 0;
		free(objectList);
		objectList = 0;
	}

	disableIO = false;
}

//----------------------------------------------------------------------------
//	begin saving the game
//----------------------------------------------------------------------------
ESaveError CSaveFile::Begin( const char *filename, bool loading )
{
	ESaveError	err = SAVEERR_OK;

	if( IsSaving() || IsLoading() )
		return SAVEERR_BUSY;

	// make sure the version # has been set up and
	// the templates have been verified
	if( saveVersion==0 )
		VerifyTemplates();

	if( loading )
	{
		pLoad = new XFILEREAD();
		if( !pLoad->Open(filename) )
			err = SAVEERR_FILENOTFOUND;
	}
	else
	{
		pSave = new XFILEWRITE();
		if( !pSave->Open(filename) )
			err = SAVEERR_CANNOTCREATEFILE;
	}

	if( err!=SAVEERR_OK )
		Reset();

	return SAVEERR_OK;
}

//----------------------------------------------------------------------------
//	end saving the game
//----------------------------------------------------------------------------
ESaveError CSaveFile::End( ESaveError returnThisError )
{
	Reset();
	return returnThisError;
}

//----------------------------------------------------------------------------
//	reading and writing data to the file
//----------------------------------------------------------------------------
ESaveError	CSaveFile::GetData( void *pData, u32 length )
{
	if( !IsLoading() )
		return SAVEERR_WRONGMODE;

	if( !IOEnabled() )
		return SAVEERR_IODISABLED;

	return pLoad->Read(pData,length)?SAVEERR_OK:SAVEERR_IOERROR;
}

ESaveError	CSaveFile::PutData( const void *pData, u32 length )
{
	if( !IsSaving() )
		return SAVEERR_WRONGMODE;

	if( !IOEnabled() )
		return SAVEERR_IODISABLED;

	return pSave->Write(pData,length)?SAVEERR_OK:SAVEERR_IOERROR;
}

//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//	declaring memory blocks
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
u32		CSaveFile::DeclareMemoryBlock( const void *pBlock, u32 length )
{
	u32		idx;

	for( idx=0;idx<numBlocks;idx++ )
	{
		if( blockList[idx].ptr==pBlock )
		{
			blockList[idx].length = Max(blockList[idx].length,length);
			return idx + 1;
		}
	}

	// allocate some more blocks
	if( numBlocks==maxBlocks )
	{
		maxBlocks = (maxBlocks==0)?16:(maxBlocks*2);
		blockList = (SAVEBLOCK*)realloc(blockList,maxBlocks*sizeof(SAVEBLOCK));
	}

	idx = numBlocks++;

	blockList[idx].ptr    = pBlock;
	blockList[idx].length = length;

	return numBlocks;
}

//----------------------------------------------------------------------------
u32		CSaveFile::DeclareMemoryBlock( const CSaveObject *pObj )
{
	const SSaveRecord	*pRec;
	const SSaveRecord	**temp;

	pRec = pObj->GetSaveTemplate();

	if( pRec==0 )
		return 0;

	//	check the save template exists in our list
	//	this allows us to be sure that it has been
	//	verified and that it is included in the
	//	version # calculation.
	for( temp=templateList;temp[0]!=0;temp++ )
	{
		if( pRec==temp[0] )
			break;
	}
	if( temp[0]==0 )
	{
		Report("*********************************\r\n");
		Report(STRING::FormatX("Missing save template %s\r\n",pRec->desc));
		Report("*********************************\r\n");
	}

	if( pRec[0].type!=ST_ClassName )
		return 0;

	return DeclareMemoryBlock(pObj,pRec[0].length);
}

//----------------------------------------------------------------------------
u32	CSaveFile::FindMemoryBlock( const void *forPtr, u32 &offset )
{
	u32			i;
	const void	*end;

	for( i=0;i<numBlocks;i++ )
	{
		// is the pointer inside this block?
		if( forPtr >= blockList[i].ptr )
		{
			end = IncPointer(blockList[i].ptr,blockList[i].length);
			if( forPtr<end )
			{
				offset = ((u8*)forPtr) - ((u8*)blockList[i].ptr);
				return i+1;
			}
		}
	}
	offset = 0;
	return 0;
}

//----------------------------------------------------------------------------
HASH CSaveFile::CreateMemoryBlockHash()
{
	HASH	ret = 0;
	u32		idx;
	for( idx=0;idx<numBlocks;idx++ )
	{
		ret = HashRotateLeft(ret,7) + blockList[idx].length;
	}
	return ret;
}

//----------------------------------------------------------------------------
const void *CSaveFile::GetPointer( u32 id, u32 offset )
{
	if( (id--)==0 )	return 0;

	assert(id<numBlocks);
	assert(offset<blockList[id].length);

	return IncPointer(blockList[id].ptr,offset);
}

//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//	declaring a savable object
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
u32 CSaveFile::DeclareSaveObject( CSaveObject *pObj )
{
	u32					idx;
	const SSaveRecord	*pRec;

	if( pObj==0 || pObj->GetSaveID()==0 )	
		return 0;

	pRec = pObj->GetSaveTemplate();
	if( pRec==0 )
		return 0;

	idx = FindSaveObject(pObj);
	if( idx!=0 )
		return idx;
	
	// allocate some more object pointers
	if( numObjects==maxObjects )
	{
		maxObjects = (maxObjects==0)?16:(maxObjects*2);
		objectList = (CSaveObject**)realloc(objectList,maxObjects*sizeof(CSaveObject*));
	}

	idx = numObjects++;
	objectList[idx] = pObj;

	return numObjects;
}

//-----------------------------------------------
// find an object's ID
//-----------------------------------------------
u32 CSaveFile::FindSaveObject( CSaveObject *pObj )
{
	u32		idx;

	if( pObj==0 )	
		return 0;

	for( idx=0;idx<numObjects;idx++ )
	{
		if( objectList[idx]==pObj )
			return idx + 1;
	}

	return 0;
}

//----------------------------------------------------------------------------
CSaveObject *CSaveFile::GetSaveObject( u32 id )
{
	if( (id--)==0 )	return 0;

	assert(id<numObjects);

	return objectList[id];
}


//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//	process the declared objects
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
ESaveError	CSaveFile::ProcessObjects()
{
	ESaveError	ret = SAVEERR_OK;
	u32			idx,num,id;
	HASH		hash;

	do
	{
		if( IsSaving() )
		{
			// pre-save all of the objects
			// cannot write to the file during this time
			DisableIO(true);
			for( idx=0;idx<numObjects;idx++ )
			{
				DeclareMemoryBlock(objectList[idx]);
				objectList[idx]->PreSave(this);
			}
			DisableIO(false);
	
			// write the # of objects out
			// followed by their SAVEID's
			PutData(numObjects);
			for( idx=0;idx<numObjects;idx++ )
			{
				id = objectList[idx]->GetSaveID();
				PutData(id);
			}
	
			// create the memory block hash
			hash = CreateMemoryBlockHash();
			PutData(hash);
	
			// save the individual objects
			for( idx=0;idx<numObjects;idx++ )
			{	// save the object
				objectList[idx]->Save(this);
			}
	
		}
		else if( IsLoading() )
		{
			numObjects = 0;	// make sure there are no objects
	
			GetData(num);	// read the # of objects
	
			// create the objects and their memory blocks
			for( idx=0;idx<num;idx++ )
			{
				GetData(id);

				// declare the object
				// cannot write to the file during this time
				DisableIO(true);
				DeclareSaveObject(CreateObject(id));
				DeclareMemoryBlock(objectList[idx]);
				objectList[idx]->PreLoad(this);
				DisableIO(false);
			}
	
			//	check the memory block hash against
			//	the one stored in the file
			GetData(hash);
			if( hash!=CreateMemoryBlockHash() )
			{
				ret = SAVEERR_MEMORYBLOCKMISMATCH;
				break;
			}
	
			// finally load all of the objects
			for( idx=0;idx<numObjects;idx++ )
			{
				objectList[idx]->Load(this);
			}
	
		}
	} while(0);

	// free all blocks and objects
	Reset(false);

	return ret;
}

//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//	change detection
//	also handles save version #
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------

bool CSaveFile::ReportUnsavedData( const SSaveRecord *classDef, u32 length, u32 offset )
{
	const SSaveRecord	*list;
	const SSaveRecord	*closest = 0;
	s32					dist,bestdist;
	char				errmsg[512];
	char				*ptr;

	if( classDef->offset==SCN_Partial )	// declared as partial class??
		return true;
	if( classDef->offset==SCN_Virtual )	// declared as virtual class??
	{
		if( offset==0 )
		{	// ignore vtable
			offset   += 4;
			length   -= 4;
		}
	}
	if( length==0 )
		return true;

	// find the closest member
	list = classDef + 1;
	while( list->type!=ST_EndOfTemplate )
	{
		dist = list->offset - offset;
		if( closest==0 || abs(dist)<abs(bestdist) )
		{
			bestdist = dist;
			closest = list;
		}
		list++;
	}

	ptr = errmsg;
	STRING::Format(ptr,"%s: %d bytes unsaved ",classDef->desc,length);
	ptr += STRING::GetLength(ptr);

	if( closest )
		STRING::Format(ptr,"%s %s\r\n",(bestdist>0)?"after":"before",closest->desc);
	else
		STRING::Format(ptr,"at %d\r\n",offset);

	Report(errmsg);
	return false;
}

//----------------------------------------------------------------------------
void CSaveFile::Report( const char *msg )
{
}

//----------------------------------------------------------------------------
bool CSaveFile::VerifyTemplates()
{
	bool				ret = true;
	const SSaveRecord	**ptr = templateList;

	saveVersion = 0;
	while( ptr[0] )
	{
		if( !VerifyTemplate(ptr[0]) )
			ret = false;
		ptr++;
	}

	if( !ret )
	{
		Report("Some templates have missing variables\r\n");
		Report("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n");
	}

	return ret;
}

//----------------------------------------------------------------------------
bool CSaveFile::VerifyTemplate( const SSaveRecord *pTemplate )
{
	const SSaveRecord	*pRec;
	unsigned char		*pMem;
	bool				ok = false;
	u32					i,len;

	if( pTemplate!=0 && pTemplate->type==ST_ClassName )
	{
		// allocate memory to check coverage
		pMem = (unsigned char *)malloc(pTemplate->length);
		if( pMem!=0 )
		{
			ok = true;

			// set the memory to 0
			memset(pMem,0,pTemplate->length);

			// write 1's everywhere that we know about
			pRec = &pTemplate[1];
			while( pRec->type!=ST_EndOfTemplate )
			{
				// encode some of the parameters of this record into the save version
				saveVersion = HashRotateLeft(saveVersion,7) + MakeStringHash(pRec->desc);
				saveVersion = HashRotateLeft(saveVersion,7) + pRec->offset;
				saveVersion = HashRotateLeft(saveVersion,7) + pRec->length;
				saveVersion = HashRotateLeft(saveVersion,7) + pRec->count;

				memset(IncPointer(pMem,pRec->offset),1,pRec->length);
				pRec++;
			}

			// report any empty locations
			for( i=0,len=0;i<pTemplate->length;i++ )
			{
				if( pMem[i]==0 )
				{	// count all empty locations
					len++;
				}
				else if( len!=0 )
				{
					if( ReportUnsavedData(pTemplate,len,i-len)==false )
						ok = false;
					len = 0;
				}
			}

			if( len!=0 )
			{
				if( ReportUnsavedData(pTemplate,len,i-len)==false )
					ok = false;
			}

			free(pMem);
		}
	}
		
	return ok;
}

//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//	template manipulation
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
//	PreSave
//----------------------------------------------------------------------------
void CSaveFile::PreSaveTemplate( const void *base, const SSaveRecord *list )
{
	const void			*src;
	u32					i;

	assert(IsSaving());
	assert(!IOEnabled());
	if( list!=0 && base!=0 )
	{
		while( list->type!=ST_EndOfTemplate )
		{
			src = IncPointer(base,list->offset);

			for( i=0;i<list->count;i++ )
			{
				if( list->type==ST_Class )
					PreSaveTemplate(src,list->pTemplate);
				else
					PreSaveRecord(src,list->type,list->length);

				src = IncPointer(src,list->length);
			}
			list++;
		}
	}
}


//----------------------------------------------------------------------------
//	PreLoad
//----------------------------------------------------------------------------
void CSaveFile::PreLoadTemplate( void *base, const SSaveRecord *list )
{
	void			*src;
	u32				i;

	assert(IsLoading());
	assert(!IOEnabled());
	if( list!=0 && base!=0 )
	{
		while( list->type!=ST_EndOfTemplate )
		{
			src = IncPointer(base,list->offset);

			for( i=0;i<list->count;i++ )
			{
				if( list->type==ST_Class )
					PreLoadTemplate(src,list->pTemplate);
				else
					PreLoadRecord(src,list->type,list->length);

				src = IncPointer(src,list->length);
			}
			list++;
		}
	}
}

//----------------------------------------------------------------------------
//	Save
//----------------------------------------------------------------------------
void CSaveFile::SaveTemplate( const void *base, const SSaveRecord *list )
{
	const void			*src;
	u32					i;

	assert(IsSaving());
	assert(IOEnabled());
	if( list!=0 && base!=0 )
	{
		while( list->type!=ST_EndOfTemplate )
		{
			src = IncPointer(base,list->offset);

			for( i=0;i<list->count;i++ )
			{
				if( list->type==ST_Class )
					SaveTemplate(src,list->pTemplate);
				else
					SaveRecord(src,list->type,list->length);

				src = IncPointer(src,list->length);
			}
			list++;
		}
	}
}

//----------------------------------------------------------------------------
//	Load
//----------------------------------------------------------------------------
void CSaveFile::LoadTemplate( void *base, const SSaveRecord *list )
{
	void			*src;
	u32				i;

	assert(IsLoading());
	assert(IOEnabled());
	if( list!=0 && base!=0 )
	{
		while( list->type!=ST_EndOfTemplate )
		{
			src = IncPointer(base,list->offset);

			for( i=0;i<list->count;i++ )
			{
				if( list->type==ST_Class )
					LoadTemplate(src,list->pTemplate);
				else
					LoadRecord(src,list->type,list->length);

				src = IncPointer(src,list->length);
			}
			list++;
		}
	}
}


//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//	RECORD functions
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
//	PreSaveRecord
//	declare any objects that are pointed to
//----------------------------------------------------------------------------
void CSaveFile::PreSaveRecord( const void *src, ESaveType type, u32 length )
{
	switch( type )
	{
	case ST_SaveObject:
		DeclareSaveObject(((CSaveObject**)src)[0]);
		break;
	}
}

//----------------------------------------------------------------------------
//	PreLoadRecord
//----------------------------------------------------------------------------
void CSaveFile::PreLoadRecord( void *src, ESaveType type, u32 length )
{
}

//----------------------------------------------------------------------------
//	SaveRecord
//----------------------------------------------------------------------------
void CSaveFile::SaveRecord( const void *src, ESaveType type, u32 length )
{
	u32		id,offset;
	switch( type )
	{
	case ST_Passive:
		PutData(src,length);
		break;

	case ST_SaveObject:
		id = FindSaveObject(((CSaveObject**)src)[0]);
		PutData(id);
		break;

	case ST_Pointer:
		id = FindMemoryBlock(((void**)src)[0],offset);
		PutData(id);
		if( id!=0 )
			PutData(offset);
		break;
	}
}

//----------------------------------------------------------------------------
//	LoadRecord
//----------------------------------------------------------------------------
void CSaveFile::LoadRecord( void *src, ESaveType type, u32 length )
{
	u32		id,offset;

	switch( type )
	{
	case ST_Passive:
		GetData(src,length);
		break;

	case ST_SaveObject:
		GetData(id);
		((CSaveObject**)src)[0] = GetSaveObject(id);
		break;

	case ST_Pointer:
		GetData(id);
		if( id!=0 )
			GetData(offset);
		((const void**)src)[0] = GetPointer(id,offset);
		break;

	}
}
